iT邦幫忙

2026 iThome 鐵人賽

DAY 24
0

在前面三天完成了:前端全日曆整合、 QR Code 簽到釋放以及 Telegram Bot 即時異動推播後,我們的 NGO 房間預約系統核心功能已全數到位。然而,在實際營運環境中,網路瞬斷、Google Sheet 讀寫失敗、Telegram API 額度限制或無效的輸入資料都可能引發未預期的異常(Exceptions)。

今天我們進入 模組 F:系統可靠性與自我診斷機制,開發全系統錯誤處理與 Log 紀錄模組——ErrorReportingService.gs。透過全域 Try-Catch 異常捕捉、Google Sheet 稽核日誌(Audit Log)與 Telegram 即時錯誤告警,打造產品級的強韌系統。


1. 例外處理與日誌監控架構設計

系統的錯誤監控與告警機制運作流程如下:

[前端/後端 API 執行] ──► [ Try-Catch 捕捉異常 ]
                              │
                              ▼
                [ ErrorReportingService.gs ]
                              │
       ┌──────────────────────┴──────────────────────┐
       ▼                                             ▼
[ 1. 寫入 "SystemLogs" Sheet ]            [ 2. Telegram 即時發送錯誤告警 ]
(保留完整 StackTrace 與 Context)           (即時通知技術管理員)

設計重點

  1. 防護不中斷(Graceful Degradation):即便 Log 寫入或 Telegram 告警失敗,也不能導致核心業務(如使用者介面)直接崩潰。
  2. 結構化日誌(Structured Logging):詳細記錄錯誤時間、函式名稱、錯誤訊息、StackTrace 以及傳入參數。
  3. 分級告警(Severity Levels):區分 INFO(一般資訊)、WARNING(警告)與 CRITICAL(嚴重錯誤),避免告警疲勞。

2. 後端 ErrorReportingService 完整程式碼 (ErrorReportingService.gs)

請在 Apps Script 專案中新增名為 ErrorReportingService.gs 的檔案,寫入以下程式碼:

/**
 * ==============================================================================
 * 全系統錯誤處理與 Log 日誌紀錄模組 (ErrorReportingService.gs)
 * 專案:NGO 房間與資源預約管理系統 (Build on Google AI Track)
 * 說明:提供統一的 Try-Catch 異常處理、Google Sheets 日誌備份與 Telegram 錯誤告警。
 * ==============================================================================
 */

/**
 * 錯誤嚴重程度枚舉
 */
const LOG_LEVEL = {
  INFO: "INFO",
  WARN: "WARNING",
  ERROR: "ERROR",
  CRITICAL: "CRITICAL"
};

/**
 * 全域錯誤處理與日誌紀錄主函式
 * @param {Error|string} error - 捕捉到的 Error 物件或錯誤字串
 * @param {string} functionName - 發生錯誤的函式名稱
 * @param {Object} [contextData] - 發生錯誤時的上下文參數 (可選)
 * @param {string} [level=LOG_LEVEL.ERROR] - 嚴重程度
 */
function logAndReportError(error, functionName, contextData = {}, level = LOG_LEVEL.ERROR) {
  const now = new Date();
  const errorMessage = error.message || error.toString();
  const stackTrace = error.stack || "無 Stack Trace";
  const contextJson = JSON.stringify(contextData);

  // 1. 輸出至 Apps Script 原生 Logger (方便線上除錯)
  Logger.log(`[${level}][${functionName}] ${errorMessage}`);

  // 2. 寫入 Google Sheets "SystemLogs" 工作表 (永久留存)
  try {
    writeLogToSheet({
      timestamp: now,
      level: level,
      functionName: functionName,
      message: errorMessage,
      context: contextJson,
      stackTrace: stackTrace
    });
  } catch (sheetError) {
    Logger.log(`❌ 寫入 SystemLogs 工作表失敗: ${sheetError.toString()}`);
  }

  // 3. 若為 ERROR 或 CRITICAL 級別,即時發送 Telegram 告警
  if (level === LOG_LEVEL.ERROR || level === LOG_LEVEL.CRITICAL) {
    try {
      sendTelegramErrorAlert(functionName, errorMessage, level, contextJson);
    } catch (tgError) {
      Logger.log(`❌ 發送 Telegram 錯誤告警失敗: ${tgError.toString()}`);
    }
  }
}

/**
 * 將日誌寫入 Google Sheets 中的 SystemLogs 工作表
 */
function writeLogToSheet(logItem) {
  const config = getConfig();
  const ss = SpreadsheetApp.openById(config.SPREADSHEET_ID);
  let sheet = ss.getSheetByName("SystemLogs");

  // 若工作表不存在則自動創建並初始化表頭
  if (!sheet) {
    sheet = ss.insertSheet("SystemLogs");
    sheet.appendRow(["Timestamp", "Level", "Function", "Message", "ContextData", "StackTrace"]);
    sheet.getRange(1, 1, 1, 6).setFontWeight("bold").setBackground("#f1f5f9");
    sheet.setFrozenRows(1);
  }

  sheet.appendRow([
    Utilities.formatDate(logItem.timestamp, "GMT+8", "yyyy-MM-dd HH:mm:ss"),
    logItem.level,
    logItem.functionName,
    logItem.message,
    logItem.context,
    logItem.stackTrace
  ]);
}

/**
 * 發送即時 Telegram 系統錯誤告警訊息
 */
function sendTelegramErrorAlert(functionName, errorMessage, level, contextJson) {
  const alertMsg = 
    `🚨 <b>【系統異常告警Notification】</b>\n` +
    `--------------------------------------\n` +
    `⚠️ <b>告警級別:</b><code>${level}</code>\n` +
    `🛠️ <b>發生模組/函式:</b><code>${functionName}</code>\n` +
    `💬 <b>錯誤訊息:</b>${errorMessage}\n` +
    `📦 <b>相關 Context:</b><pre>${contextJson}</pre>\n` +
    `⏰ <b>發生時間:</b>${Utilities.formatDate(new Date(), "GMT+8", "yyyy-MM-dd HH:mm:ss")}\n` +
    `--------------------------------------\n` +
    `<i>請技術管理員儘速排查系統日誌。</i>`;

  // 呼叫 Day 23 的 Telegram 推播函式
  sendTelegramNotification(alertMsg);
}

/**
 * 包裝業務邏輯的安全執行器 (Higher-Order Function 概念)
 * 用於統一捕捉任何後端 API 的非預期例外
 */
function safeExecute(fn, functionName, fallbackValue = { success: false, message: "系統繁忙中,請稍後再試。" }, contextData = {}) {
  try {
    return fn();
  } catch (error) {
    logAndReportError(error, functionName, contextData, LOG_LEVEL.ERROR);
    return fallbackValue;
  }
}


3. 在既有 Service 中套用 ErrorReportingService

以下示範如何將 ErrorReportingService.gs 無縫嵌入之前的核心業務模組中:

範例:重構 BookingService.gs 中的 API 進入點

/**
 * 提交新預約的安全 API 進入點 (示範 ErrorReportingService 的整合)
 */
function submitNewBookingSafe(userEmail, roomId, startTimeIso, endTimeIso, purpose) {
  const contextData = { userEmail, roomId, startTimeIso, endTimeIso, purpose };

  return safeExecute(function() {
    // 呼叫原本 Day 19 的預約邏輯
    return submitNewBooking(userEmail, roomId, startTimeIso, endTimeIso, purpose);
  }, "submitNewBookingSafe", { success: false, message: "預約處理失敗,技術團隊已收到告警通知。" }, contextData);
}


4. 單元測試與驗證

你可在 Apps Script 中執行以下模擬測試,驗證當系統發生未預期錯誤時,是否能正確寫入 SystemLogs 工作表並推播 Telegram 告警:

/**
 * 錯誤監控模組單元測試
 */
function testErrorLoggingAndAlert() {
  Logger.log("=== 開始錯誤監控測試 ===");

  try {
    // 故意觸發未定義變數之 ReferenceError
    let result = undefinedVariable.doSomething();
  } catch (error) {
    logAndReportError(
      error, 
      "testErrorLoggingAndAlert", 
      { testCase: "模擬前端傳入無效 JSON", userId: "worker_01" }, 
      LOG_LEVEL.ERROR
    );
  }

  Logger.log("測試完成,請檢查 Google Sheets 中的 SystemLogs 工作表與 Telegram 告警訊息。");
}


5. 小結

今天我們完成了 全系統錯誤處理、例外監控與 Log 日誌紀錄。藉由將全域 Try-Catch、SystemLogs 稽核軌跡與 Telegram 即時告警結合,大幅提升了系統的魯棒性(Robustness)與維運效率。

明天繼續……


上一篇
【Day 23】Telegram Bot 即時通知與狀態異动推播機制
下一篇
【Day 25】系統整合測試、資料一致性與備份機制
系列文
零預算 NGO 數位轉型挑戰:30 天打造智慧訂房系統 共 30 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言